#pragma once

#include <Windows.h>
#include <mmsystem.h>
#include <mmdeviceapi.h>
#include <Audioclient.h>
#include <functiondiscoverykeys.h>
#include <Avrt.h>
#include <gcroot.h>
#include <vector>

#pragma comment(lib, "Avrt.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "winmm.lib")

namespace VirtualPedal
{

using namespace System;


template<class T>
struct ComPtr
{
private:
	T* _Ptr;
public:
	ComPtr() : _Ptr(NULL) { }
	ComPtr(T* ptr) : _Ptr(ptr)
	{
		if(_Ptr) { _Ptr->AddRef(); }
	}
	ComPtr(const ComPtr<T>& other) : _Ptr(other._Ptr)
	{
		if(_Ptr) { _Ptr->AddRef(); }
	}
	ComPtr<T>& operator=(const ComPtr<T>& other)
	{
		if(_Ptr) { _Ptr->Release(); }
		_Ptr = other._Ptr;
		if(_Ptr) { _Ptr->AddRef(); }
		return *this;
	}
	ComPtr<T>& operator=(T* ptr)
	{
		if(_Ptr) { _Ptr->Release(); }
		_Ptr = ptr;
		if(_Ptr) { _Ptr->AddRef(); }
		return *this;
	}
	T* operator->() { return _Ptr; }

	bool operator==(T* other) const
	{
		return _Ptr == other;
	}

	bool operator==(const ComPtr<T>& other) const
	{
		return _Ptr == other._Ptr;
	}

	bool operator!=(const ComPtr<T>& other) const
	{
		return _Ptr != other._Ptr;
	}
	bool operator<(const ComPtr<T>& other) const
	{
		return _Ptr < other._Ptr;
	}
	bool operator>(const ComPtr<T>& other) const
	{
		return _Ptr > other._Ptr;
	}

	operator bool()
	{
		return (_Ptr != NULL);
	}

	operator T*()
	{
		return _Ptr;
	}

	template<class U>
	U* As()
	{
		return static_cast<U*>(_Ptr);
	}

	T* GetRawPtr() const
	{
		return _Ptr;
	}

	//First time to use, not calling AddRef
	Void Init(T* ptr)
	{
		if(_Ptr)
			SafeRelease(_Ptr);
		_Ptr = ptr;
	}

	T*const* GetAddressOf() const
	{
		return &_Ptr;
	}

	T** GetAddressOf()
	{
		return &_Ptr;
	}

	T** ReleaseAndGetAddressOf()
	{
		if(_Ptr) _Ptr->Release();
		_Ptr = NULL;
		return &_Ptr;
	}

	~ComPtr()
	{
		if(_Ptr) { _Ptr->Release(); }
	}
};

template<class T>
ref class CliComPtr
{
	ComPtr<T>* m_p;
internal:

	ComPtr<T>& Ptr() { return *m_p; }

	CliComPtr()
	{
		m_p = new ComPtr<T>();
	}

	~CliComPtr()
	{
		if(m_p)
		{
			delete m_p;
			m_p = nullptr;
		}
	}

	!CliComPtr()
	{
		if(m_p)
		{
			delete m_p;
			m_p = nullptr;
		}
	}
};


public ref class VPException : public System::Exception
{
	HRESULT m_Hr;
	String^ m_Msg;
internal:
	VPException(HRESULT hr, String^ msg) : m_Hr(hr)
	{
		m_Msg = msg;
	}
public:
	property Int32 Code {
		Int32 get() { return (Int32)m_Hr; }
	}

	virtual String^ ToString() override
	{
		return m_Msg;
	}
};

#define DECL_ERR( r ) { r, L#r },

inline void ThrowOnError(HRESULT hr)
{
	struct ErrToMsgTag
	{
		HRESULT R;
		LPCTSTR T;
	} ErrToMsg[] =
	{
		DECL_ERR(E_POINTER)
		DECL_ERR(E_INVALIDARG)
		DECL_ERR(AUDCLNT_E_DEVICE_INVALIDATED)
		DECL_ERR(AUDCLNT_E_SERVICE_NOT_RUNNING)
	};

	if(FAILED(hr))
	{
		String^ msg = L"UnKnown";
		for(int i=0; i<sizeof(ErrToMsg) / sizeof(ErrToMsgTag); i++)
		{
			if(ErrToMsg[i].R == hr)
			{
				msg = gcnew String(ErrToMsg[i].T);
			}
		}
		throw gcnew VPException(hr, msg);
	}
}

}